﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Windows.Forms;


namespace TextGenerator
{
    public partial class MainForm : Form
    {
        #region Redraw Prevention.
        /*
         * From:
         * http://weblogs.asp.net/jdanforth/archive/2004/03/12/88458.aspx
         */
        private const int WM_SETREDRAW = 0x000B;
        private const int WM_USER = 0x400;
        private const int EM_GETEVENTMASK = (WM_USER + 59);
        private const int EM_SETEVENTMASK = (WM_USER + 69);

        [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")]
        [DllImport("user32", CharSet = CharSet.Auto)]
        private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);

        private IntPtr eventMask = IntPtr.Zero;
        #endregion Redraw Prevention.

        #region RichTextBox Handlers.
        private void rtbPattern_TextChanged(object sender, EventArgs e)
        {
            // Temporarily stop drawing the control to reduce flickering.
            StopRedrawing();

            // Backup the users current selection point.
            int selectionStart = this.rtbPattern.SelectionStart;
            int selectionLength = this.rtbPattern.SelectionLength;

            // Reset the current formatting.
            this.rtbPattern.SelectionStart = 0;
            this.rtbPattern.SelectionLength = this.rtbPattern.TextLength;
            this.rtbPattern.SelectionFont = this.fntRegular;
            this.rtbPattern.SelectionColor = this.clrRegular;

            // Initialize variables for counting statistics.
            int cntVariables = 0;
            int cntPermutations = 0;

            // Format all tokens.
            int[,] indices = Permutator.GetIndices(this.rtbPattern.Text);
            if (indices != null)
            {
                for (int i = 0; i < indices.GetLength(0); i++)
                {
                    this.rtbPattern.SelectionStart = indices[i, 0];
                    this.rtbPattern.SelectionLength = indices[i, 1];

                    // Format the symbol based on its type.
                    switch (indices[i, 2])
                    {
                        case 0:
                            // Regular token.
                            this.rtbPattern.SelectionColor = this.clrToken;
                            this.rtbPattern.SelectionFont = this.fntToken;
                            break;
                        case 1:
                            // Reference token.
                            this.rtbPattern.SelectionColor = this.clrReference;
                            this.rtbPattern.SelectionFont = this.fntReference;
                            break;
                        default:
                            // Regular text.
                            this.rtbPattern.SelectionColor = this.clrRegular;
                            this.rtbPattern.SelectionFont = this.fntRegular;
                            break;
                    }
                }

                cntVariables = Permutator.CountVariables(this.rtbPattern.Text);
                try
                {
                    cntPermutations = Permutator.CountPermutations(this.rtbPattern.Text);
                }
                catch (OverflowException)
                {
                    MessageBox.Show(
                        ERROR_TOO_MANY_COMBINATIONS_TEXT, 
                        ERROR_TOO_MANY_COMBINATIONS_TITLE,
                        MessageBoxButtons.OK, 
                        MessageBoxIcon.Warning, 
                        MessageBoxDefaultButton.Button1);
                }
            }

            // Display the statistics.
            this.tsslVariables.Text = String.Format("{0}: {1:N0}", LABEL_VARIABLES_TEXT, cntVariables);
            this.tsslPermutations.Text = String.Format("{0}: {1:N0}", LABEL_COMBINATIONS_TEXT, cntPermutations);

            // Restore the users current selection point.
            this.rtbPattern.SelectionStart = selectionStart;
            this.rtbPattern.SelectionLength = selectionLength;

            // Restore the regular formatting.
            this.rtbPattern.SelectionColor = this.clrRegular;
            this.rtbPattern.SelectionFont = this.fntRegular;

            // Enable/disable the menu item depending of what there is to do.
            this.generateToolStripMenuItem.Enabled = (cntPermutations > 0);

            // Draw the control.
            StartRedrawing();
        }

        private void rtbPattern_KeyDown(object sender, KeyEventArgs e)
        {
            // Make sure Control is pressed.
            if (!e.Control)
                return;

            // Get the control.
            RichTextBox rtb = (RichTextBox) sender;

            // Determine what key was pressed.
            switch (e.KeyCode)
            {
                case Keys.V:
                    // Paste the text without any formatting.

                    // Suspend the layout to prevent flickering.
                    rtb.SuspendLayout();

                    // If any text is selected, replace it by removing it.
                    rtb.SelectedText = String.Empty;

                    // Get the insertion point.
                    int start = rtb.SelectionStart;

                    // Get the length of the trailing text.
                    int trail = rtb.Text.Length - start;

                    // Concatenate the new text.
                    // Retrieve the clipboard data as text.
                    rtb.Text = String.Format("{0}{1}{2}", rtb.Text.Substring(0, start), Clipboard.GetData(DataFormats.Text), rtb.Text.Substring(start));

                    // Adjust the insertion point to just after the inserted text.
                    rtb.SelectionStart = rtb.TextLength - trail;

                    // Resume the layout.
                    rtb.ResumeLayout();

                    // Cancel the paste.
                    e.Handled = true;
                    break;
                case Keys.X:
                    // Cut the selected text without the formatting.

                    // Copy the selected text.
                    Clipboard.SetText(rtb.SelectedText, TextDataFormat.Text);

                    // Calculate the indices for the selected text.
                    int selectionStart = rtb.SelectionStart;
                    int selectionEnd = rtb.SelectionStart + rtb.SelectionLength;

                    // Cut the selected text.
                    rtb.Text = String.Format("{0}{1}", rtb.Text.Substring(0, rtb.SelectionStart), rtb.Text.Substring(selectionEnd, rtb.TextLength - selectionEnd));

                    // Restore the cursor's position.
                    rtb.SelectionStart = selectionStart;

                    // Tag the event as handled to prevent the control from 
                    // handling it itself. But, don't suppress the key press, 
                    // to allow the control to detect the text changed-event 
                    // and update the formatting.
                    e.Handled = true;
                    break;
                case Keys.C:
                    // Copy the text without formatting.

                    // Don't do anything if nothing is selected.
                    if (String.IsNullOrEmpty(rtb.SelectedText))
                        return;

                    // Set the clipboard's text.
                    Clipboard.SetText(rtb.SelectedText, TextDataFormat.Text);

                    // Tag the event as handled, 
                    // and suppress the key press as no more processing is needed.
                    e.Handled = true;
                    e.SuppressKeyPress = true;
                    break;
                case Keys.Z:
                    // Ignore the Undo-shortcut.
                    e.Handled = true;
                    e.SuppressKeyPress = true;
                    break;
                default:
                    return;
            }
        }
        #endregion RichTextBox Handlers.

        #region Helper Methods.
        /// <summary>
        /// Halts the graphical update of the pattern text box.
        /// </summary>
        private void StopRedrawing()
        {
            // Stop redrawing.
            SendMessage(this.rtbPattern.Handle, WM_SETREDRAW, 0, IntPtr.Zero);

            // Stop sending of events.
            eventMask = SendMessage(this.rtbPattern.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
        }

        /// <summary>
        /// Resumes the graphical update of the pattern text box.
        /// </summary>
        private void StartRedrawing()
        {
            // Turn on events.
            SendMessage(this.rtbPattern.Handle, EM_SETEVENTMASK, 0, eventMask);

            // Turn on redrawing.
            SendMessage(this.rtbPattern.Handle, WM_SETREDRAW, 1, IntPtr.Zero);

            // Invalidate and redraw.
            this.rtbPattern.Refresh();
        }
        #endregion Helper Methods.
    }
}